package wangmin;

import java.security.SecureRandom;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

/**
 * @author wangmin
 * @discription 星期 0~6 : 0 代表星期天; 1 星期一; 2 星期二; 3 星期三; 4 星期四; 5 星期五; 6 星期六;
 * @created 2023-06-07 9:50
 */
public abstract class TestDayOfWeek {
    // 蔡勒（Zeller）公式 计算
    // 改函数的复杂度  略高于16 (月不是 1,2是16次)
    public static int dayOfWeekByZeller(int year, int month, int day) {
        //if (year < 1600 || year >= 3000) throw new RuntimeException(year + " 年超出计算范围");

        if (month == 1) {   // 1次运算
            --year; // 1次运算
            month = 13; // 1次运算
        } else if (month == 2) { // 1次运算
            --year; // 1次运算
            month = 14; // 1次运算
        }

        int c = year/100;   // 无需加1   1次运算
        int y = year % 100;  // 1次运算
        int d = day;

        // 加上 700 是为了避免 世纪c过大 时 结果为负值
        // 12 次运算, d-1+700  编译器优化为 d+699
        return (
                y + (y/4) + (c/4) - 2*c + (26*(month+1)/10) + d-1    + 700
               ) % 7;
    }



    private static final byte d16000101 = 6;
    private static final byte[] yearInfoArr = new byte[801];
    static {
        int last = d16000101;
        int lastYearIsLeap = 1;
        yearInfoArr[0] = (byte) (d16000101 | (1 << 3));
        // 以4年为一个周期循环
        for (int i=1; i<801;) {
            // 判断上一年是否闰年
            last = (last + 1 + lastYearIsLeap)%7;
            yearInfoArr[i++] = (byte)last;

            last = (last + 1)%7;    // 本来是 (last + 365)%7,  但是 364 正好被7整除
            yearInfoArr[i++] = (byte)last;

            last = (last + 1)%7;
            yearInfoArr[i++] = (byte)last;

            last = (last + 1)%7;
            int year = 1600+i;
            lastYearIsLeap = (year%4==0 && year%100!=0) || (year%400==0) ? 1 : 0;
            yearInfoArr[i] = (byte) (last | (lastYearIsLeap << 3));   // 同时将是否闰年的信息加上
            ++i;
        }
    }
    private static final short[] dayIndexOfYear_not_leap = new short[32*13];
    private static final short[] dayIndexOfYear_leap = new short[32*13];
    private static int rowIdxOfYear(int month, int day) {
        return (month << 5) | day;
    }
    static {
        short[] dayOfMonthNotLeap = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
        short indexOfYear = 0;
        for (int monthIdx=0; monthIdx<12; ++monthIdx) {
            int monthFirstDayRowIdx = rowIdxOfYear(monthIdx+1, 1);
            final short dayOfMonth = dayOfMonthNotLeap[monthIdx];
            for (int i = 0; i < dayOfMonth; ++i) {
                dayIndexOfYear_not_leap[monthFirstDayRowIdx] = indexOfYear;
                ++monthFirstDayRowIdx;
                ++indexOfYear;
            }
        }

        short[] dayOfMonthLeap = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
        indexOfYear = 0;
        for (int monthIdx=0; monthIdx<12; ++monthIdx) {
            int monthFirstDayRowIdx = rowIdxOfYear(monthIdx+1, 1);
            final short dayOfMonth = dayOfMonthLeap[monthIdx];
            for (int i = 0; i < dayOfMonth; ++i) {
                dayIndexOfYear_leap[monthFirstDayRowIdx] = indexOfYear;
                ++monthFirstDayRowIdx;
                ++indexOfYear;
            }
        }
    }
    public static int dayOfWeekByData(int year, int month, int day) {
        //if (year < 1600 || year > 2400) throw new RuntimeException(year + " 年超出计算范围");

        final byte yearInfo = yearInfoArr[year - 1600]; // 2次运算 : 减法和寻址

        // 判断 leap一次, 计算坐标3次, 寻址一次
        int dayIndexOfYear = (month << 5) | day;  // 2次运算
        if ((yearInfo >> 3) != 0) {    // 是否闰年   1次运算
            dayIndexOfYear = dayIndexOfYear_leap[dayIndexOfYear];   // 1次运算 寻址
        } else {
            dayIndexOfYear = dayIndexOfYear_not_leap[dayIndexOfYear];   // 1次运算 寻址
        }

        return ((yearInfo & 7) + dayIndexOfYear) % 7; // 3次运算
    }



    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");    // 注意该数据结构无法并发, 只能单线程
    public static int dayOfWeekByCalendar(int year, int month, int day) throws ParseException {
        Date date = sdf.parse(String.format("%d%02d%02d", year, month, day));

        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        return cal.get(Calendar.DAY_OF_WEEK) - 1;
    }



    private final static SecureRandom random = new SecureRandom();
    public static void main(String[] args) throws ParseException {
        final int testSize = 10000000;

        short[] testData_year = new short[testSize];
        byte[] testData_month = new byte[testSize];
        byte[] testData_day = new byte[testSize];
        for (int i=0; i<testSize; ++i) {
            testData_year[i] = (short) (1600 + random.nextInt(800));
            testData_month[i] = (byte) (1 + random.nextInt(12));
            testData_day[i] = (byte) (1 + random.nextInt(28));
        }

        // 验证正确性
        /*for (int i=0; i<testSize; ++i) {
            int z = dayOfWeekByZeller(testData_year[i], testData_month[i], testData_day[i]);
            int d = dayOfWeekByData(testData_year[i], testData_month[i], testData_day[i]);
            if (z != d) {
                int c = dayOfWeekByCalendar(testData_year[i], testData_month[i], testData_day[i]);
                System.out.println("dayOfWeekByZeller = " + z + ", dayOfWeekByData = "+ d + ", dayOfWeekByCalendar = " + c);
            }
        }*/

        // 稳定消耗 3 毫秒
        long startMS = System.currentTimeMillis();
        for (int i=0; i<testSize; ++i) {
            dayOfWeekByZeller(testData_year[i], testData_month[i], testData_day[i]);
        }
        System.out.println("dayOfWeekByZeller, spend time : " + (System.currentTimeMillis() - startMS));

        // 稳定消耗 32 毫秒
        startMS = System.currentTimeMillis();
        for (int i=0; i<testSize; ++i) {
            dayOfWeekByData(testData_year[i], testData_month[i], testData_day[i]);
        }
        System.out.println("dayOfWeekByData, spend time : " + (System.currentTimeMillis() - startMS));

        // !!! 结论, 虽然 dayOfWeekByData 运算次数少于 dayOfWeekByZeller, 但是因为 寻址运算消耗时间远远大于算数运算, 导致总得时间差一个数量级
    }

}
